//****************************************************
// Porman Project 2
// Filename: CShaderObject.cpp
// Date: 2008/01/13
// Author: KengYu Lin
// Modify:
//****************************************************
#include "CRenderDevice.h"
#include "CShaderObject.h"
#include "CTextureObject.h"

namespace Porman
{

namespace GL
{

CShaderObject::CShaderObject( CRenderDevice* pDevice, EShaderType enumType ) :
CBaseObject(pDevice),
m_eType(enumType),
m_cgProgram(NULL),
m_vectSamplerParams(),
m_vectInstParams(),
m_vectSemantic()
{
    setName("Non");
    m_iNumOfShaderObj++;
}

CShaderObject::~CShaderObject( void )
{
    m_vectSemantic.clear();
    _clearParam();
    cgDestroyProgram( m_cgProgram );
    m_iNumOfShaderObj--;
}

bool CShaderObject::isCompiled( void )
{
    return cgIsProgramCompiled( m_cgProgram ) ? true : false;
}

std::string CShaderObject::getCode( EShaderCodeType enumCodeType )
{
    std::string sCode = "";
    switch( enumCodeType )
    {
    case E_SHADERCODE_TYPE_SOURCE:
        sCode = cgGetProgramString( m_cgProgram, CG_PROGRAM_SOURCE );
    case E_SHADERCODE_TYPE_ENTRY:
        sCode = cgGetProgramString( m_cgProgram, CG_PROGRAM_ENTRY );
    case E_SHADERCODE_TYPE_COMPILEDSOURCE:
        if( isCompiled() ) sCode = cgGetProgramString( m_cgProgram, CG_COMPILED_PROGRAM );
        break;
    }

    return sCode;
}

ERESULT CShaderObject::setArraySize( const HSHADERPARAM* pParam, const int& iSize )
{
    if( pParam && pParam->isVaild() )
    {
        cgSetArraySize( pParam->m_cgParam, iSize );
        return E_SUCCESS; 
    }

    assert(0);
    return E_INVALID_VALUE;
}

ERESULT CShaderObject::setArrayMultiDimSize( const HSHADERPARAM* pParam, const int* pSize )
{
    if( pParam && pParam->isVaild() && pSize )
    {
        cgSetMultiDimArraySize( pParam->m_cgParam, pSize );
        return E_SUCCESS; 
    }

    assert(0);
    return E_INVALID_VALUE;
}

ERESULT CShaderObject::setParameterFloat( const HSHADERPARAM* pParam, float fVal )
{
    ERESULT enumResult = E_INVALID_VALUE;
    if( pParam && pParam->isVaild() )
    {
        cgSetParameter1f( pParam->m_cgParam, fVal );
        switch( cgGetError() )
        {
        case CG_NO_ERROR:
            return E_SUCCESS;
        default:
            enumResult = E_INVALID_PARAMETER;
            break;
        }
    }

    assert(0);
    return enumResult;
}

ERESULT CShaderObject::setParameterFloatArrayRow( const HSHADERPARAM* pParam, unsigned int iNumElement, const float* fArray )
{
    ERESULT enumResult = E_INVALID_VALUE;
    if( pParam && pParam->isVaild() && fArray )
    {
        cgSetParameterValuefr( pParam->m_cgParam, iNumElement, fArray );
        switch( cgGetError() )
        {
        case CG_NO_ERROR:
            return E_SUCCESS;
        case CG_INVALID_PARAMETER_ERROR :
            enumResult = E_INVALID_PARAMETER;
            break;
        case CG_NOT_ENOUGH_DATAFORMAT_ERROR:
            enumResult = E_NOT_ENOUGH_DATA;
            break;
        case CG_NON_NUMERIC_PARAMETER_ERROR:
            enumResult = E_NON_NUMERIC_PARAMETER;
            break;
        }
    }

    assert(0);
    return enumResult;
}

ERESULT CShaderObject::setParameterFloatArrayCol( const HSHADERPARAM* pParam, unsigned int iNumElement, const float* fArray )
{
    ERESULT enumResult = E_INVALID_VALUE;
    if( pParam && pParam->isVaild() && fArray )
    {
        cgSetParameterValuefc( pParam->m_cgParam, iNumElement, fArray );
        switch( cgGetError() )
        {
        case CG_NO_ERROR:
            return E_SUCCESS;
        case CG_INVALID_PARAMETER_ERROR :
            enumResult = E_INVALID_PARAMETER;
            break;
        case CG_NOT_ENOUGH_DATAFORMAT_ERROR:
            enumResult = E_NOT_ENOUGH_DATA;
            break;
        case CG_NON_NUMERIC_PARAMETER_ERROR:
            enumResult = E_NON_NUMERIC_PARAMETER;
            break;
        }
    }

    assert(0);
    return enumResult;
}

ERESULT CShaderObject::setSampler( const HSHADERPARAM* pParam, const CSamplerState* pSampler, CTextureBaseObject* pTextureObj )
{
    ERESULT enumResult = E_INVALID_VALUE;
    if( pParam && pParam->isVaild() && pTextureObj && pTextureObj->_setSamplerState( (*pSampler) ) == E_SUCCESS )
    {
        cgGLSetTextureParameter( pParam->m_cgParam, pTextureObj->m_uiObjID );
        cgGLEnableTextureParameter( pParam->m_cgParam );

        bool bCheck = false;
        int iCount = static_cast<unsigned int>( m_vectSamplerParams.size() );
        for( int i = 0; i<iCount; i++ )
        {
            if( m_vectSamplerParams[i].m_cgParam == pParam->m_cgParam )
            {
                bCheck = true; 
                break;
            }
        }

        if( !bCheck )
            m_vectSamplerParams.push_back( (*pParam) );

        return E_SUCCESS;
    }

    assert(0);
    return enumResult;
}

ERESULT CShaderObject::setInterface( HSHADERPARAM* pInterface, const HSHADERPARAM* pParam )
{
    if( pInterface && pInterface->isVaild() && pParam && pParam->isVaild() )
    {
        cgConnectParameter( pParam->m_cgParam, pInterface->m_cgParam );
        return E_SUCCESS;
    }

    assert(0);
    return E_INVALID_VALUE;
}

ERESULT CShaderObject::setArraySize( const char* sParam, const int& iSize )
{
    ERESULT enumResult = E_INVALID_VALUE;
    HSHADERPARAM hParam;
    if( getParameter( sParam, &hParam ) == E_SUCCESS )
    {
        if( setArraySize( &hParam, iSize ) == E_SUCCESS )
            enumResult = E_SUCCESS;
    }

    assert(enumResult == E_SUCCESS);
    return enumResult;
}

ERESULT CShaderObject::setArrayMultiDimSize( const char* sParam, const int* pSize )
{
    ERESULT enumResult = E_INVALID_VALUE;
    HSHADERPARAM hParam;
    if( getParameter( sParam, &hParam ) == E_SUCCESS )
    {
        if( setArrayMultiDimSize( &hParam, pSize ) == E_SUCCESS )
            enumResult = E_SUCCESS;
    }

    assert(enumResult == E_SUCCESS);
    return enumResult;
}

ERESULT CShaderObject::setParameterFloat( const char* sParam, float fVal )
{
    ERESULT enumResult = E_INVALID_VALUE;
    HSHADERPARAM hParam;
    if( getParameter( sParam, &hParam ) == E_SUCCESS )
    {
        if( setParameterFloat( &hParam, fVal ) == E_SUCCESS )
            enumResult = E_SUCCESS;
    }

    assert(enumResult == E_SUCCESS);
    return enumResult;
}

ERESULT CShaderObject::setParameterFloatArrayRow( const char* sParam, unsigned int iNumElement, const float* fArray )
{
    ERESULT enumResult = E_INVALID_VALUE;
    HSHADERPARAM hParam;
    if( getParameter( sParam, &hParam ) == E_SUCCESS )
    {
        if( setParameterFloatArrayRow( &hParam, iNumElement, fArray ) == E_SUCCESS )
            enumResult = E_SUCCESS;
    }

    assert(enumResult == E_SUCCESS);
    return enumResult;
}

ERESULT CShaderObject::setParameterFloatArrayCol( const char* sParam, unsigned int iNumElement, const float* fArray )
{
    ERESULT enumResult = E_INVALID_VALUE;
    HSHADERPARAM hParam;
    if( getParameter( sParam, &hParam ) == E_SUCCESS )
    {
        if( setParameterFloatArrayCol( &hParam, iNumElement, fArray ) == E_SUCCESS )
            enumResult = E_SUCCESS;
    }

    assert(enumResult == E_SUCCESS);
    return enumResult;
}

ERESULT CShaderObject::setSampler( const char* sParam, const CSamplerState* pSampler, CTextureBaseObject* pTextureObj )
{
    ERESULT enumResult = E_INVALID_VALUE;
    HSHADERPARAM hParam;
    if( getParameter( sParam, &hParam ) == E_SUCCESS )
    {
        if( setSampler( &hParam, pSampler, pTextureObj ) == E_SUCCESS )
            enumResult = E_SUCCESS;
    }

    assert(enumResult == E_SUCCESS);
    return enumResult;
}

ERESULT CShaderObject::setInterface( const char* sInterface, const HSHADERPARAM* pParam )
{
    ERESULT enumResult = E_INVALID_VALUE;
    HSHADERPARAM hParam;
    if( getParameter( sInterface, &hParam ) == E_SUCCESS )
    {
        if( setInterface( &hParam, pParam ) == E_SUCCESS )
            enumResult = E_SUCCESS;
    }

    assert(enumResult == E_SUCCESS);
    return enumResult;
}

ERESULT CShaderObject::getParameter( const char* sParam, HSHADERPARAM* pParam )
{
    if( sParam && pParam )
    {
        CGparameter cgParam = cgGetNamedParameter( m_cgProgram, sParam );
        if( cgParam )
        {
            pParam->m_cgParam = cgParam;
            pParam->m_sName = sParam;
            return E_SUCCESS;
        }
    }

    assert(0);
    return E_INVALID_VALUE;
}

ERESULT CShaderObject::getArrayDim( const char* sParam, int& iDim )
{
    iDim = 0;
    HSHADERPARAM hParam;
    if( getParameter( sParam, &hParam ) == E_SUCCESS )
    {
        return getArrayDim( &hParam, iDim );
    }

    assert(0);
    return E_INVALID_VALUE;
}

ERESULT CShaderObject::getArrayDim( HSHADERPARAM* pParam, int& iDim )
{
    iDim = 0;
    if( pParam )
    {
        iDim = cgGetArrayDimension( pParam->m_cgParam );
        return E_SUCCESS;
    }

    assert(0);
    return E_INVALID_VALUE;
}

ERESULT CShaderObject::getArraySize( const char* sParam, int iDim,int& iSize )
{
    iSize = 0;
    HSHADERPARAM hParam;
    if( getParameter( sParam, &hParam ) == E_SUCCESS )
    {
        getArraySize( &hParam, iDim, iSize );
    }

    assert(0);
    return E_INVALID_VALUE;
}

ERESULT CShaderObject::getArraySize( HSHADERPARAM* pParam, int iDim,int& iSize )
{
    iSize = 0;
    if( pParam )
    {
        iSize = cgGetArraySize( pParam->m_cgParam, iDim );
    }

    assert(0);
    return E_INVALID_VALUE;
}

ERESULT CShaderObject::getInstance( const char* sInstance, HSHADERPARAM* pParam )
{
    if( sInstance && pParam )
    {
        CGcontext* pContextRef = m_pDeviceRef->_getShaderContext();
        CGtype cgType = cgGetNamedUserType( m_cgProgram, sInstance );
        if( cgType != CG_UNKNOWN_TYPE )
        {
            CGparameter cgParam = cgCreateParameter( (*pContextRef), cgType );
            m_vectInstParams.push_back(cgParam);

            pParam->m_cgParam = cgParam;
            pParam->m_sName = cgGetParameterName( pParam->m_cgParam );
            return E_SUCCESS;
        }
    }

    assert(0);
    return E_INVALID_VALUE;
}

ERESULT CShaderObject::getInstanceArray( const char* sInstance, int iLength, HSHADERPARAM* pParam )
{
    if( sInstance && pParam )
    {
        CGcontext* pContextRef = m_pDeviceRef->_getShaderContext();
        CGtype cgType = cgGetNamedUserType( m_cgProgram, sInstance );
        if( cgType != CG_UNKNOWN_TYPE )
        {
            CGparameter cgParam = cgCreateParameterArray( (*pContextRef), cgType, iLength );
            m_vectInstParams.push_back(cgParam);

            pParam->m_cgParam = cgParam;
            pParam->m_sName = cgGetParameterName( pParam->m_cgParam );
            return E_SUCCESS;
        }
    }

    assert(0);
    return E_INVALID_VALUE;
}

ERESULT CShaderObject::getInstanceMultiDimArray( const char* sInstance, int iDim, const int* pLength, HSHADERPARAM* pParam )
{
    if( sInstance && pParam )
    {
        CGcontext* pContextRef = m_pDeviceRef->_getShaderContext();
        CGtype cgType = cgGetNamedUserType( m_cgProgram, sInstance );
        if( cgType != CG_UNKNOWN_TYPE )
        {
            CGparameter cgParam = cgCreateParameterMultiDimArray( (*pContextRef), cgType, iDim, pLength );
            m_vectInstParams.push_back(cgParam);

            pParam->m_cgParam = cgParam;
            pParam->m_sName = cgGetParameterName( pParam->m_cgParam );
            return E_SUCCESS;
        }
    }

    assert(0);
    return E_INVALID_VALUE;
}

ERESULT CShaderObject::getInstanceParam( const char* sInstance, const HSHADERPARAM* pInParam, HSHADERPARAM* pOutParam )
{
    if( sInstance && pInParam && pOutParam )
    {
        CGtype cgType = cgGetParameterType( pInParam->m_cgParam );
        if( cgType == CG_STRUCT )
        {
            std::string sInstParamName = pInParam->m_sName + "." + std::string( sInstance );
            CGparameter cgParam = cgGetFirstStructParameter( pInParam->m_cgParam );
            while( cgParam )
            {
                std::string sParamName = cgGetParameterName( cgParam );
                if( sInstParamName == sParamName )
                {
                    pOutParam->m_cgParam = cgParam;
                    pOutParam->m_sName = sInstance;
                    return E_SUCCESS;
                }
                cgParam = cgGetNextParameter( cgParam );
            }
        }
    }

    assert(0);
    return E_INVALID_VALUE;
}

void CShaderObject::_disableAllSamplerParameters( void )
{
    int iCount = static_cast<unsigned int>( m_vectSamplerParams.size() );
    for( int i = 0; i<iCount; i++ )
        cgGLDisableTextureParameter( m_vectSamplerParams[i].m_cgParam );
}

void CShaderObject::_clearParam( void )
{
    int iCount = static_cast<int>(m_vectInstParams.size());
    for( int i=0; i<iCount; i++ )
        cgDestroyParameter( m_vectInstParams[i] );

    m_vectInstParams.clear();
    m_vectSamplerParams.clear();
}

void CShaderObject::_enableVaryingShaderParameters( void )
{
    m_vectSemantic.clear();
    CGparameter cgParam = cgGetFirstLeafParameter( m_cgProgram, CG_PROGRAM );
    while( cgParam )
    {
        if( cgIsParameterReferenced( cgParam ) && cgGetParameterDirection( cgParam ) == CG_IN  )
        {
            std::string sSemantic = cgGetParameterSemantic( cgParam );
            if( sSemantic != "" )
            {
                SSemantic semantic( sSemantic.c_str(), cgParam );
                m_vectSemantic.push_back( semantic );
            }
        }
        cgParam = cgGetNextLeafParameter( cgParam );
    }
}

void CShaderObject::_disableVaryingShaderParameters( void )
{
    int iSizeSemantic = static_cast<int>( m_vectSemantic.size() );
    for( int k=0; k<iSizeSemantic; k++ )
    {
        if( m_vectSemantic[k].m_bEnable )
        {
            cgGLDisableClientState( m_vectSemantic[k].m_cgParam );
            m_vectSemantic[k].m_bEnable = false;
        }
    }
}

int CShaderObject::_getSemanticSize( void )
{
    return static_cast<int>(m_vectSemantic.size());
}

SSemantic* CShaderObject::_getSemantic( int iIndex )
{
    if( iIndex < 0 || iIndex >= _getSemanticSize() )
    {
        assert(0);         
        return NULL;
    }
    else
        return &m_vectSemantic[iIndex];
}

}//End namespace GL

}//End namespace Porman